package com.uwo.commons.datasource.configuration;

import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.filter.logging.Slf4jLogFilter;
import com.alibaba.druid.pool.DruidDataSource;
import io.shardingjdbc.core.api.ShardingDataSourceFactory;
import io.shardingjdbc.core.api.config.MasterSlaveRuleConfiguration;
import io.shardingjdbc.core.api.config.ShardingRuleConfiguration;
import io.shardingjdbc.core.api.config.TableRuleConfiguration;
import io.shardingjdbc.core.api.config.strategy.InlineShardingStrategyConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.*;

/**
 * 数据源配置
 * @author hao.yan
 */
@Order(1)
@Configuration
public class DataSourceConfiguration {

    private final Logger log = LoggerFactory.getLogger(DataSourceConfiguration.class);

    @Autowired
    private DataSourceProperties dataSourceProperties;

    /**
     * 获取数据源
     * @return
     */
    @Bean(name = "dataSource")
    public DataSource dataSource() throws SQLException{
        if(!dataSourceProperties.getIsMultiSource()){
            // 数据源
            Map<String, DataSource> dataSources = initDataSources();
            // 分库分表策略
            ShardingRuleConfiguration shardingRuleConfiguration = initRules();
            return ShardingDataSourceFactory.createDataSource(dataSources, shardingRuleConfiguration,
                    new LinkedHashMap<String, Object>(), new Properties());
        }
        if(CollectionUtils.isEmpty(dataSourceProperties.getSources()))
            return null;

        // 数据源配置
        DataSourceProperties.SourceProperties properties = dataSourceProperties.getSources().get(0);
        return dataSource(properties);
    }


    private Filter filter(){
        Slf4jLogFilter slf4jLogFilter = new Slf4jLogFilter();
        slf4jLogFilter.setConnectionLogEnabled(false);
        slf4jLogFilter.setStatementLogEnabled(false);
        slf4jLogFilter.setResultSetLogEnabled(true);
        slf4jLogFilter.setStatementExecutableSqlLogEnable(true);
        return slf4jLogFilter;
    }

    private DataSource dataSource(DataSourceProperties.SourceProperties properties){
        return dataSource(properties, Arrays.asList(filter()));
    }

    /**
     * 构建数据源
     * @param properties
     * @param filters
     * @return
     */
    private DataSource dataSource(DataSourceProperties.SourceProperties properties, List<Filter> filters){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(properties.getUrl());
        dataSource.setUsername(properties.getUsername());
        dataSource.setPassword(properties.getPassword());
        dataSource.setDriverClassName(properties.getDriverClassName());
        dataSource.setInitialSize(dataSourceProperties.getPoolProperties().getInitialSize());
        dataSource.setMinIdle(dataSourceProperties.getPoolProperties().getMinIdle());
        dataSource.setMaxActive(dataSourceProperties.getPoolProperties().getMaxActive());
        dataSource.setMaxIdle(dataSourceProperties.getPoolProperties().getMaxIdle());
        dataSource.setMaxWait(dataSourceProperties.getPoolProperties().getMaxWait());
        dataSource.setTimeBetweenEvictionRunsMillis(dataSourceProperties.getPoolProperties().getTimeBetweenEvictionRunsMillis());
        dataSource.setMinEvictableIdleTimeMillis(dataSourceProperties.getPoolProperties().getMinEvictableIdleTimeMillis());
        dataSource.setValidationQuery(dataSourceProperties.getPoolProperties().getValidationQuery());
        dataSource.setTestWhileIdle(dataSourceProperties.getPoolProperties().getTestWhileIdle());
        dataSource.setTestOnBorrow(dataSourceProperties.getPoolProperties().getTestOnBorrow());
        dataSource.setTestOnReturn(dataSourceProperties.getPoolProperties().getTestOnBorrow());
        dataSource.setPoolPreparedStatements(dataSourceProperties.getPoolProperties().getPoolPreparedStatements());
        dataSource.setMaxPoolPreparedStatementPerConnectionSize(dataSourceProperties.getPoolProperties().getMaxPoolPreparedStatementPerConnectionSize());
        try {
            dataSource.setFilters(dataSourceProperties.getPoolProperties().getFilters());
        } catch (SQLException e) {
            e.printStackTrace();
        }
        dataSource.setProxyFilters(filters);
        return dataSource;
    }

    /**
     * 初始化数据源
     */
    private Map<String, DataSource> initDataSources() {
        if(CollectionUtils.isEmpty(dataSourceProperties.getSources())){
            return null;
        }
        Map<String, DataSource> dataSources = new LinkedHashMap<String, DataSource>();
        // 保存数据源到集合
        for (DataSourceProperties.SourceProperties properties:dataSourceProperties.getSources()) {
            log.debug("data source name : {}", properties.getName());
            dataSources.put(properties.getName(), dataSource(properties));
        }
        return dataSources;
    }

    /**
     * 策略
     * @return
     */
    private ShardingRuleConfiguration initRules(){
        DataSourceProperties.RuleProperties ruleProperties = dataSourceProperties.getRule();
        if(null == ruleProperties){
            return null;
        }

        ShardingRuleConfiguration result = new ShardingRuleConfiguration();

        DataSourceProperties.StrategyProperties databaseStrategyProperties = ruleProperties.getDatabaseStrategy();
        if (null != databaseStrategyProperties) {
            result.setDefaultDatabaseShardingStrategyConfig(
                    new InlineShardingStrategyConfiguration(databaseStrategyProperties.getColumn(),
                            databaseStrategyProperties.getAlgorithmExpression()));
        }

        DataSourceProperties.StrategyProperties tableStrategyProperties = ruleProperties.getTableStrategy();
        if (null != tableStrategyProperties) {
            result.setDefaultTableShardingStrategyConfig(
                    new InlineShardingStrategyConfiguration(tableStrategyProperties.getColumn(),
                            tableStrategyProperties.getAlgorithmExpression()));
        }

        for (DataSourceProperties.TableProperties tableProperties: ruleProperties.getTables()) {
            TableRuleConfiguration tableRuleConfig = new TableRuleConfiguration();
            tableRuleConfig.setLogicTable(tableProperties.getLogicTable());
            tableRuleConfig.setActualDataNodes(tableProperties.getActualDataNodes());

            databaseStrategyProperties = selectStrategyProperties(tableProperties.getDatabaseStrategy(), databaseStrategyProperties);
            if (null != databaseStrategyProperties) {
                tableRuleConfig.setDatabaseShardingStrategyConfig(
                        new InlineShardingStrategyConfiguration(databaseStrategyProperties.getColumn(),
                                databaseStrategyProperties.getAlgorithmExpression()));
            }

            tableStrategyProperties = selectStrategyProperties(tableProperties.getTableStrategy(), tableStrategyProperties);
            if (null != tableStrategyProperties) {
                log.debug("tableStrategyProperties = {}", tableStrategyProperties);
                tableRuleConfig.setTableShardingStrategyConfig(
                        new InlineShardingStrategyConfiguration(tableStrategyProperties.getColumn(),
                                tableStrategyProperties.getAlgorithmExpression()));
            }
            if(!StringUtils.isEmpty(tableProperties.getKeyGeneratorClass())){
                tableRuleConfig.setKeyGeneratorClass(tableProperties.getKeyGeneratorClass());
            }
            tableRuleConfig.setKeyGeneratorColumnName(tableProperties.getKeyGenerator());
            tableRuleConfig.setLogicIndex(tableProperties.getLogicIndex());
            result.getTableRuleConfigs().add(tableRuleConfig);
        }

        Map<String, DataSourceProperties.MasterSlaveProperties> masterSlavePropertiesMap = ruleProperties.getMasterSlaves();
        Collection<MasterSlaveRuleConfiguration> masterSlaveRuleConfigs = new LinkedList<MasterSlaveRuleConfiguration>();
        for (Map.Entry<String, DataSourceProperties.MasterSlaveProperties> entry : masterSlavePropertiesMap.entrySet()) {
            DataSourceProperties.MasterSlaveProperties masterSlaveProperties = entry.getValue();
            MasterSlaveRuleConfiguration masterSlaveRuleConfiguration = new MasterSlaveRuleConfiguration();
            masterSlaveRuleConfiguration.setName(entry.getKey());
            masterSlaveRuleConfiguration.setMasterDataSourceName(masterSlaveProperties.getMasterName());
            masterSlaveRuleConfiguration.setSlaveDataSourceNames(masterSlaveProperties.getSlaveNames());
            masterSlaveRuleConfigs.add(masterSlaveRuleConfiguration);
        }
        result.setMasterSlaveRuleConfigs(masterSlaveRuleConfigs);
        return result;
    }


    private DataSourceProperties.StrategyProperties selectStrategyProperties(DataSourceProperties.StrategyProperties strategyProperties, DataSourceProperties.StrategyProperties defaultStrategyProperties){
        return (null == strategyProperties) ? defaultStrategyProperties : strategyProperties;
    }

}
